package com.dommettj.rawfix.session;

import com.dommettj.rawfix.message.*;
import com.dommettj.rawfix.settings.Settings;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class NioSession implements Session, MessageHandler {

    private static final Logger logger = Logger.getLogger(NioSession.class);
    private final Map<MessageType, Collection<MessageHandler>> externalMessageHandlers;
    private final MessageGenerator messageGenerator;
    private String hostName;
    private int port;
    private boolean isActive = false;
    private HeartbeatWorker heartbeat;
    private NioSessionWorker worker;
    private Thread sessionThread;

    public NioSession(final String hostName, final int port, final MessageGenerator messageGenerator) throws IOException {
        this.externalMessageHandlers = new HashMap<MessageType, Collection<MessageHandler>>();
        this.messageGenerator = messageGenerator;
        this.hostName = hostName;
        this.port = port;
    }

    @Override
    public void start() throws SessionException, IOException {

        if(isActive) {
            tearDownWorkers();
        }

        worker = new NioSessionWorker(hostName, port);
        heartbeat = new HeartbeatWorker(this, messageGenerator);

        registerInternalMessageHandler(MessageType.LOGON, heartbeat);
        registerInternalMessageHandler(MessageType.HEARTBEAT, heartbeat);
        registerInternalMessageHandler(MessageType.LOGOFF, this);

        for(Map.Entry<MessageType, Collection<MessageHandler>> e : externalMessageHandlers.entrySet()) {
            for(MessageHandler h : e.getValue()) {
                registerInternalMessageHandler(e.getKey(), h);
            }
        }

        sessionThread = new Thread(worker);
        sessionThread.start();

        final String logonMessage = messageGenerator.createLogonMessage();
        try {
            worker.sendMessage(logonMessage);
        } catch (ClosedChannelException e) {
            throw new SessionException("Failed to send message: " + logonMessage, e);
        }
        isActive = true;
    }

    @Override
    public void stop() throws SessionException {

        final String logoffMessage = messageGenerator.createLogoffMessage();
        sendMessage(logoffMessage);
        isActive = false;
    }

    @Override
    public boolean isActive() {
        return isActive;
    }

    @Override
    public String constructMessage(String messageBody) throws MalformedMessageException {
        return messageGenerator.constructMessage(messageBody);
    }

    @Override
    public void registerMessageHandler(MessageType messageType, MessageHandler messageHandler) {
        Collection<MessageHandler> handlers = externalMessageHandlers.get(messageType);
        if(handlers == null) {
            handlers = new ArrayList<MessageHandler>();
            externalMessageHandlers.put(messageType, handlers);
        }
        handlers.add(messageHandler);
    }

    @Override
    public void sendMessage(String message) throws SessionException {

        if(isActive()) {
            try {
                worker.sendMessage(message);
            } catch (ClosedChannelException e) {
                throw new SessionException("Failed to send message: " + message, e);
            }
        } else {
            throw new SessionException("Not logged on, cannot send message");
        }
    }

    @Override
    public void notifySessionSettingsUpdated(Settings settings) {
        this.hostName = settings.loadHost();
        this.port = Integer.parseInt(settings.loadPort());
        this.messageGenerator.notifySettingsUpdated(settings);
    }

    @Override
    public void handle(String message) {

        try {
            if(MessageUtils.getMessageType(message) == MessageType.LOGOFF) {
                logger.debug("Logoff message received");
                tearDownWorkers();
                messageGenerator.resetMessageSequence();
            } else if(MessageUtils.getMessageType(message) == MessageType.LOGON) {
                logger.debug("Logon message received");
                isActive = true;
            }
        } catch (UnknownMessageTypeException e) {
            logger.error("Failed to handle message", e);
        }
    }

    private void tearDownWorkers() {
        heartbeat.stop();
        worker.requestShutdown();
        sessionThread.interrupt();
    }

    private void registerInternalMessageHandler(final MessageType messageType, final MessageHandler messageHandler) {
        worker.registerMessageHandler(messageType, messageHandler);
    }

}
